import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;


public class TcpClient {
    public interface Message{
        void onRecvMessage(String message);
    }

    private class ReadMessage implements Runnable {
        private TcpClient _client = null;

        public ReadMessage(TcpClient client) {
            _client = client;
            return;
        }

        private void readSockDataInputUTF8Message() {
            if (_client != null) {
                try {
                    if (_client.getInputStream() != null) {
                        DataInputStream dins = new DataInputStream(_client.getInputStream());
                        if (0 < dins.available()) {
                            //String message = dins.readUTF();
                            //System.out.println("read message len: " + message.length() + ", content: " + message);
                            //_client._messge.onRecvMessage(message);
                            _client._messge.onRecvMessage(dins.readUTF()); // it will block the thread
                        } else {
                            //System.out.println("data inputstream is not available.");
                            Thread.sleep(100);
                        }
                    } else {
                        //System.out.println("tcpclient inputstream instance is null.");
                        Thread.sleep(100);
                    }
                } catch (IOException e) {
                    //e.printStackTrace();
                    System.out.println(e.getMessage());
                    Thread.currentThread().interrupt();
                } catch (InterruptedException e) {
                    //e.printStackTrace();
                    System.out.println(e.getMessage());
                }
            } else {
                System.out.println("tcpclient instance is null.");
            }

            return;
        }

        private void readSockDataInputStreamMessage() {
            if (_client != null) {
                try {
                    if (_client.getInputStream() != null) {
                        DataInputStream dins = new DataInputStream(_client.getInputStream());
                        int len = dins.available();
                        if (0 < len) {
                            byte[] data = new byte[len];
                            dins.read(data);
                            //String message = data.toString();
                            String message = new String(data, StandardCharsets.UTF_8);
                            //System.out.println("read message len: " + message.length() + ", content: " + message);
                            _client._messge.onRecvMessage(message);
                        } else {
                            //System.out.println("data inputstream is not available.");
                            Thread.sleep(100);
                        }
                    } else {
                        //System.out.println("tcpclient inputstream instance is null.");
                        Thread.sleep(100);
                    }
                } catch (IOException e) {
                    //e.printStackTrace();
                    System.out.println(e.getMessage());
                    Thread.currentThread().interrupt();
                } catch (InterruptedException e) {
                    //e.printStackTrace();
                    System.out.println(e.getMessage());
                }
            } else {
                System.out.println("tcpclient instance is null.");
            }

            return;
        }

        private void readSockInputStreamMessage() {
            if (_client != null) {
                try {
                    if (_client.getInputStream() != null) {
                        int len = _client.getInputStream().available();
                        if (0 < len) {
                            byte[] data = new byte[len];
                            _client.getInputStream().read(data);
                            //String message = data.toString();
                            String message = new String(data, StandardCharsets.UTF_8);
                            //System.out.println("read message len: " + message.length() + ", content: " + message);
                            _client._messge.onRecvMessage(message);
                        } else {
                            //System.out.println("data inputstream is not available.");
                            Thread.sleep(100);
                        }
                    } else {
                        //System.out.println("tcpclient inputstream instance is null.");
                        Thread.sleep(100);
                    }
                } catch (IOException e) {
                    //e.printStackTrace();
                    System.out.println(e.getMessage());
                    Thread.currentThread().interrupt();
                } catch (InterruptedException e) {
                    //e.printStackTrace();
                    System.out.println(e.getMessage());
                }
            } else {
                System.out.println("tcpclient instance is null.");
            }

            return;
        }

        @Override
        public void run() {
            if (_client != null) {
                System.out.println("read tcp message thread is running.....");

                while (_client.isConnected()) {
                    /*if (_client.isServerClosed()) {
                        System.out.println("tcp server is closed this connection.");
                        _client.disconnect();
                        break;
                    }*/

                    //readSockDataInputUTF8Message();
                    //readSockDataInputStreamMessage();
                    readSockInputStreamMessage();
                }

                System.out.println("read tcp message thread is stoped.");
            } else {
                System.out.println("tcpclient instance is null.");
            }

            return;
        }
    }

    private volatile boolean _connected = false;
    private String _srvIP = null;
    private int _srvPort = 0;

    private Socket _sock = null;
    private InputStream _ins = null;
    private OutputStream _ous = null;
    //private ReadMessage _readMessage = null;

    public Message _messge = null;

    public void setMessageCallback(Message message) {
        _messge = message;
        return;
    }

    public boolean isConnected() {
        return _connected;
    }

    public boolean isServerClosed() {
        boolean isClosed = false;
        /*try {
            _sock.sendUrgentData(0xFF);
        } catch (Exception e) {
            isClosed = true;
            System.out.println(e.getMessage());
        }*/

        try {
            // when _sock.getInputStream().read() < 0, it is closed by remote socket.
            if (_sock.getInputStream().read() < 0) {
                isClosed = true;
            }
        } catch (IOException e) {
            isClosed = true;
            System.out.println(e.getMessage());
            //throw new RuntimeException(e);
        }

        return isClosed;
    }

    public String getTcpServerInfo() {
        return _srvIP + ":" + _srvPort;
    }

    public InputStream getInputStream() {
        return _ins;
    }

    public boolean connect(String srvIP, int srvPort) {
        if (!_connected) {
            _srvIP = srvIP;
            _srvPort = srvPort;

            try {
                _sock = new Socket(_srvIP, _srvPort);
                _sock.setKeepAlive(true);

                _ins = _sock.getInputStream();
                _ous = _sock.getOutputStream();

                _connected = (_ins != null && _ous != null);
                if (_connected) {
                    System.out.println("Connect to server: " + _srvIP + ":" + _srvPort + " success!");
                    // start receive message thread
                    new Thread(new ReadMessage(this)).start();
                }

            } catch (IOException e) {
                System.out.println(e.getMessage());
            }
        }

        return _connected;
    }

    public void disconnect() {
        if (_connected) {
            try {
                _connected = false;

                if (_ous != null) {
                    _ous.flush();
                    _ous.close();
                    _ous = null;
                }

                if (_ins != null) {
                    _ins.close();
                    _ins = null;
                }

                if (_sock != null) {
                    _sock.shutdownInput();
                    _sock.shutdownOutput();
                    _sock.close();
                    _sock = null;
                }

                System.out.println("Disconnect to server: " + _srvIP + ":" + _srvPort + ".");
            } catch (IOException e) {
                System.out.println(e.getMessage());
            }
        }

        return;
    }

    public void writeStringMessage(String message) {
        if (message != null) {
            if (_connected) {
                try {
                    if (_ous != null) {
                        _ous.write(message.getBytes());
                        _ous.flush();
                    }
                } catch (IOException e) {
                    System.out.println(e.getMessage());
                }
            }
        }

        return;
    }

    public void writeBytesMessage(byte[] message) {
        if (message != null) {
            if (_connected) {
                try {
                    if (_ous != null) {
                        _ous.write(message);
                        _ous.flush();
                    }
                } catch (IOException e) {
                    System.out.println(e.getMessage());
                }
            }
        }

        return;
    }

}